package br.com.loja.util.reflection;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

/**
 * @brief Classe ReflectionUtil
 * @author David Luiz
 * @date 03/12/2014
 */
public class ReflectionUtil {

	private static final int SET_INIT_SIZE = 190;
	public static final int GET_METHOD = 0;
	public static final int SET_METHOD = 1;
	private static final Set<String> allClasses;
	private static final Set<String> entitiesClass;

	static {
		allClasses = buildAllClasses();
		entitiesClass = buildEntitiesClass();
	}

	public static Set<String> getAllClasses() {
		return allClasses;
	}

	public static Method getMethodByName(Class<?> clazz, String methodName,
			Class<?>... paramType) {
		Method method = null;
		if (clazz != null) {
			try {
				method = clazz.getDeclaredMethod(methodName, paramType);
			} catch (SecurityException e) {
				System.err.println(e.getMessage());
				e.printStackTrace();
			} catch (NoSuchMethodException ex) {
				method = getMethodByName(clazz.getSuperclass(), methodName,
						paramType);
			}
		}
		return method;
	}

	public static Field getFieldByName(Class<?> clazz, String attribute) {
		Field field = null;
		if (clazz != null) {
			try {
				field = clazz.getDeclaredField(attribute);
			} catch (SecurityException e) {
				System.err.println(e.getMessage());
				e.printStackTrace();
			} catch (NoSuchFieldException ex) {
				field = getFieldByName(clazz.getSuperclass(), attribute);
			}
		}
		return field;
	}

	public static <T extends Annotation> List<Method> getMethodByAnnotation(
			Class<?> clazz, Class<T> annotationClass) {

		List<Method> list = new ArrayList<>();

		if (clazz != null) {
			try {

				Method methods[] = clazz.getDeclaredMethods();

				for (Method m : methods) {

					T annotation = m.getAnnotation(annotationClass);

					if (annotation != null) {
						list.add(m);
					}
				}

				list.addAll(getMethodByAnnotation(clazz.getSuperclass(),
						annotationClass));

			} catch (SecurityException e) {
				// implementar log
				System.err.println(e.getMessage());
				e.printStackTrace();
			}
		}

		return list;
	}

	public static <T extends Annotation> List<Field> getFieldByAnnotation(
			Class<?> clazz, Class<T> annotationClass) {

		List<Field> list = new ArrayList<>();

		if (clazz != null) {
			try {

				Field fields[] = clazz.getDeclaredFields();

				for (Field f : fields) {

					T annotation = f.getAnnotation(annotationClass);

					if (annotation != null) {
						list.add(f);
					}
				}

				list.addAll(getFieldByAnnotation(clazz.getSuperclass(),
						annotationClass));

			} catch (SecurityException e) {
				// implementar log
				System.err.println(e.getMessage());
				e.printStackTrace();
			}
		}

		return list;
	}

	static public String getMethodNameByField(Field field, int type) {

		String methodName;
		String prefix = "";

		methodName = String.valueOf(field.getName().charAt(0)).toUpperCase()
				+ field.getName().substring(1);

		if (type == GET_METHOD) {
			if (field.getType().getCanonicalName().equals("java.lang.Boolean")) {
				prefix = "is";
			} else {
				prefix = "get";
			}
		} else if (type == SET_METHOD) {
			prefix = "set";
		}

		return prefix.concat(methodName);
	}

	private synchronized static Set<String> buildAllClasses() {

		ClassLoader classLoader = ClassLoader.getSystemClassLoader();

		if (!(classLoader instanceof URLClassLoader)) {
			return null;
		}

		Set<String> list = new HashSet<>(SET_INIT_SIZE);

		URL[] urls = ((URLClassLoader) classLoader).getURLs();

		for (URL url : urls) {

			File location = null;

			try {

				String path = url.getPath().replace("%20", " ");
				System.out.println(path);

				if (path.endsWith(".jar")) {
					location = new File(path);
				}

				if (location == null) {
					location = new File(url.toURI());
				}

			} catch (URISyntaxException e) {
				System.err.println("ERROR: " + e.toString());
				e.printStackTrace();
				continue;
			}

			if (location.isDirectory() || isJarFile(location)) {
				list.addAll(getClasses(null, location));
			}

		}

		System.out.println("Classes carregadas: " + list.size());

		return list;
	}

	private static boolean isJarFile(File location) {
		return location.getName().endsWith(".jar");
	}

	/**
	 * getClasses
	 *
	 * @param parent
	 *            String
	 * @param location
	 *            File
	 */
	public static List<String> getClasses(String parent, File location) {

		List<String> classes = new ArrayList<>();

		if (location.isDirectory()) {

			File[] files = location.listFiles();

			for (File file : files) {

				classes.addAll(getClassInFile(parent, file));

			}
		} else {

			classes.addAll(getClassInFile(parent, location));

		}

		return classes;
	}

	private static List<String> getClassInFile(String parent, File file) {

		List<String> classes = new ArrayList<>();
		String path;

		if (parent == null) {

			path = file.getName();

		} else {

			StringBuilder builder = new StringBuilder();
			builder.append(parent).append(".").append(file.getName());
			path = builder.toString();

		}

		if (file.isDirectory()) {

			classes.addAll(getClasses(path, file));

		} else if (file.getName().endsWith(".class")) {

			int indexChar = path.indexOf("$");

			if (isNotOutOfBound(indexChar)) {

				path = path.substring(0, indexChar);

			}

			if (path.contains("class")) {

				classes.add(path.substring(0, path.indexOf(".class")));

			}
		} else if (isJarFile(file)) {

			classes.addAll(getClassesInJar(file));

		}
		return classes;
	}

	private static boolean isNotOutOfBound(int indexChar) {
		return indexChar != -1;
	}

	public static List<String> getClassesInJar(File location) {

		List<String> classes = new ArrayList<>();

		try (JarFile jar = new JarFile(location)) {

			Enumeration<?> entries = jar.entries();

			while (entries.hasMoreElements()) {
				ZipEntry entry = (ZipEntry) entries.nextElement();
				String name = entry.getName();

				name = name.replace("/", ".");

				// implemetar configuração para pacotes permitidos
				if ((!name.startsWith("br.com.loja"))) {
					continue;
				}

				if (name.endsWith(".class")) {
					int p = name.indexOf("$");

					if (isNotOutOfBound(p)) {
						name = name.substring(0, p);
					}

					if (name.contains(".class")) {
						classes.add(name.substring(0, name.indexOf(".class")));
					}
				}

			}
		} catch (IOException e) {
			// implementar log
			System.err.println(e.toString());
		}

		return classes;
	}

	public static <T extends Annotation> Set<Class<?>> getAllClassByAnnotation(
			Class<T> annotationClass) {

		Set<Class<?>> classes = new HashSet<>();

		for (String className : getAllClasses()) {

			Class<?> loadClass = null;

			try {
				loadClass = ClassLoader.getSystemClassLoader().loadClass(
						className);
			} catch (ClassNotFoundException ex) {
				// implementa log
				System.out.println(ex.getMessage());
				ex.printStackTrace();
			}

			if (loadClass == null) {
				continue;
			}

			if (loadClass.getAnnotation(annotationClass) != null) {
				classes.add(loadClass);
			}

		}

		return classes;
	}

	public static Object getBeanInstance(String className) {
		ClassLoader classLoader = ClassLoader.getSystemClassLoader();
		Object instance = null;
		try {
			instance = classLoader.loadClass(className).newInstance();
		} catch (InstantiationException | IllegalAccessException
				| ClassNotFoundException e) {
			System.err.println(e.getMessage());
			e.printStackTrace();
		}
		return instance;
	}

	public static List<Field> getAllFields(Class<?> clazz) {

		List<Field> list = new ArrayList<>();

		if (clazz != null) {
			try {

				Field fields[] = clazz.getDeclaredFields();

				list.addAll(Arrays.asList(fields));
				list.addAll(getAllFields(clazz.getSuperclass()));

			} catch (SecurityException e) {
				// implementar log
				System.err.println(e.getMessage());
				e.printStackTrace();
			}
		}

		return list;

	}

	private static Set<String> buildEntitiesClass() {
		ClassLoader classLoader = ClassLoader.getSystemClassLoader();
		Set<String> set = new HashSet<>();
		try {
			SAXReader reader = new SAXReader();
			Document doc = reader.read(classLoader
					.getResource("META-INF/persistence.xml"));
			Element root = doc.getRootElement();
			Element element = root.element("persistence-unit");
			for (Iterator<?> it = element.elements().iterator(); it.hasNext();) {
				Element e = (Element) it.next();
				if (e.getName().equals("class")) {
					set.add(e.getStringValue());
				}
			}
		} catch (DocumentException e) {
			throw new IllegalArgumentException(
					"erro ao tentar encontrar arquivo", e);
		}
		return set;
	}

	public static boolean isEntity(Class<?> clazz) {
		return entitiesClass.contains(clazz.getName());
	}

}
